--[[
 * =====================================================================================
 *
 *       Filename:  environment.lua
 *
 *    Description:  Lua environment test
 *
 *        Version:  1.0
 *        Created:  2014/11/25 18:38:21
 *       Revision:  none
 *
 *         Author:  jacy sun (https://github.com/elloop), elloop@foxmail.com
 *   Organization:  China
 *
 * =====================================================================================
--]]

-- defines p, str and other convinient shortcut function
require("print_util")
-- redefined the function, because it will be easy to modify the codes using _p once the _p function name changed.
local p = _p
local str = _str
local pc = _pc

local env = {}

-- ============================================================
-- print _G
-- ============================================================
function env:print_all_global()
    pc("all symbols in _G:")
    for k in pairs(_G) do
        p(str(k))
    end
    pc("end all symbols in _G")
end

function env:get_field(field) 
    local result = _G
    for w in string.gmatch(field, "[%w_]+") do
        p("get field: " .. str(w))
        result = result[w]
    end
    return result
end

function env:set_field(field, value)
    local result = _G
    for w, d in string.gmatch(field, "([%w_]+)(%.?)") do
        if '.' == d then
            result[w] = result[w] or {}
            result = result[w]
        else
            result[w] = value
        end
    end
end

-- change the metatable of _G
-- 所有对_G中不存在字段的访问，都将导致一个错误
function env:protect_global_visit()
    -- _G 原来的metatable是nil.
    setmetatable(_G, {
        -- todo: use str(k) in case k is a table ( other may use a table as an index)
        __index = function (_, k)
            error("attempt to read undeclared variable: " .. k, 2)
        end,

        __newindex = function (_, k) 
            error("attempt to write to undeclared variable: " .. k, 2)
        end
    })
end

-- recover visit to _G.
function env:recover_global_visit()
    setmetatable(_G, nil)
end

-- how to declare a var when _G is in protect-mode 
function env:declare(name, value)
    rawset(_G, name, value or false) -- 'or false' to ensure the var won't be set to nil.
end

function env:declare_allow_nil(name, value)
    rawset(_G, name, value)
end

local delclared = {}
-- to make it possible to declare a nil global var, use following metatable for _G.
function env:protect_global_visit_allow_nil()
    setmetatable(_G, {
        __newindex = function (t, k, v)
            if not declared[k] then
                local which_chunk = debug.getinfo(2, "S").what
                if which_chunk ~= "main" and which_chunk ~= "C" then
                    error("attempt to write to undeclared variable: " .. k, 2)
                else
                    declared[k] = true
                end
            end
            rawset(t, k, v)
        end,

        __index = function (_, k)
            if not declared[k] then
                error("attempt to read undeclared variable: " .. k, 2)
            else
                return nil
            end
        end
    }
    )
end

function env:ensure_set_global_in_main_chunk()
    setmetatable(_G, {
        __newindex = function(t, k, v)
            local which_chunk = debug.getinfo(2, "S").what
            p("which_chunk: (in ensure_set_global_in_main_chunk)" .. str(which_chunk))
            if which_chunk ~= "main" and which_chunk ~= "C" then
                error("attempt to write to undeclared variable: " .. str(k), 2)
            end
            rawset(t, k, v)
        end,

        __index = function (_, k)
            error("attempt to read undeclared variable: " .. str(k), 2)
        end,
    })
end

local environment_test = env
return environment_test

